#include "../main.h"

struct T {
	JSContext *cx;
	jsval func;
	pthread_t tid;
	uint32 t;
	jsval rval;
	
	int id;
	int kill;
};

static pthread_mutex_t m;


JSBool branch_callback(JSContext *cx, JSScript *script){
	trace1("branch_callback");
	struct T *x = JS_GetContextPrivate(cx);
	if(x)
		if (x->kill==1) {
			trace2("killing %i", x->id);
//pthread_mutex_lock(&m);
			x->kill = 0;
//pthread_mutex_unlock(&m);
			return JS_FALSE;
		}
	return JS_TRUE;
}



#define MAX_THREADS 5000

struct T *slots[MAX_THREADS];
static int slots_last=0;

static int free_slots[MAX_THREADS];
static int free_slots_last=0;

struct T *get_slot(JSContext *cx){
	trace1("get_slot");
	struct T *x;
	int id;
	if(slots_last>=MAX_THREADS) {
		trace2("slots limit exceded");
		return NULL;
	}
	if(free_slots_last>0) {
		trace2("reusing existing slot");
		x = slots[id=free_slots[--free_slots_last]];
	} else {
		trace2("creating new context");
//		JS_BeginRequest(cx);
		x = calloc(sizeof(struct T), 1);
		x->cx = JS_NewContext(JS_GetRuntime(cx), 8192);
		JS_SetScriptStackQuota(x->cx, 128*1024*1024);
		JS_SetGlobalObject(x->cx, JS_GetGlobalObject(cx));
//		JS_ClearContextThread(x->cx);
		id = slots_last++;
		slots[id] = x;
		x->id = id;
	}
	
	JS_ClearContextThread(x->cx);
//pthread_mutex_lock(&m);
	x->kill = 0;
//pthread_mutex_unlock(&m);
	JS_AddRoot(x->cx, (void *)&x->rval);
	JS_AddRoot(x->cx, (void *)&x->func);
	trace2("slot_id=%i", x->id);
	return x;
}

int release_slot(int id){
	trace1("release_slot", "id=%i", id);
	struct T *x = slots[id];
	JS_RemoveRoot(x->cx, (void *)&x->func);
	JS_RemoveRoot(x->cx, (void *)&x->rval);
//	x->tid = NULL;
//pthread_mutex_lock(&m);
	free_slots[free_slots_last++] = id;
//pthread_mutex_unlock(&m);
	return 0;
}


void *thread_proc(void *param){	

	struct T *x = (struct T *)param;
	trace1("thread_proc", "*t=%lu", x->t);
		
	
	JS_SetContextThread(x->cx);
	JS_BeginRequest(x->cx);	
	JS_SetContextPrivate(x->cx, x);
	x->kill = 0;

	if(x->t){
		jsrefcount rc = JS_SuspendRequest(x->cx);
		usleep(1000*x->t);
		JS_ResumeRequest(x->cx, rc);
	}
	
	JSObject *obj=JS_GetGlobalObject(x->cx);

	if(!JS_CallFunctionValue(x->cx, obj, x->func, 0, JSVAL_NULL, &x->rval)){
		trace2("function failed");
		JS_GetPendingException(x->cx, &x->rval);
		JS_ClearPendingException(x->cx);
	}
	trace2("ending request");
	JS_EndRequest(x->cx);
	JS_ClearContextThread(x->cx);
//	JS_SetBranchCallback(x->cx, NULL);
	
	release_slot(x->id);
	
	trace2("thread %i done", x->id);
	pthread_exit(NULL);
	return NULL;
};


static JSBool SetTimeout(JSContext *cx, uintN argc, jsval *vp) {
	trace1("SetTimeout", "enter");

//	return JS_TRUE;

	struct T *x = get_slot(cx);
//	JS_BeginRequest(cx);
		
	if (!JS_ConvertValue(cx, JS_ARGV(cx, vp)[0], JSTYPE_FUNCTION, &x->func)){
		release_slot(x->id);
		return JS_FALSE;
	}
	if(!JS_ConvertArguments(cx, 2, JS_ARGV(cx, vp)+1, "u", &x->t)){
		release_slot(x->id);
		return JS_FALSE;
	}
//	JS_EndRequest(cx);
//	trace1("setTimeout", "t=%u", x->t);

//	JS_ClearContextThread(x->cx);
	
	pthread_attr_t pattr;
	pthread_attr_init(&pattr);
	pthread_attr_setdetachstate(&pattr, PTHREAD_CREATE_DETACHED);	
	pthread_create(&x->tid, &pattr, thread_proc, x);

	JS_SET_RVAL(cx, vp, INT_TO_JSVAL(x->id));
	return JS_TRUE;
}	






static JSBool ClearTimeout(JSContext *cx, uintN argc, jsval *vp) {
	trace1("clearTimeout", "enter");
	int id;
	if(JS_ConvertArguments(cx, 2, JS_ARGV(cx, vp), "u", &id)) {
		struct T *x = slots[id];
		x->kill = 1;
		JS_SetBranchCallback(x->cx, branch_callback);
	}

	return JS_TRUE;
}	



/*
constructor(){
	JSObject *this;
	
	
}

	
*/

int module_thread_init(JSContext *cx, JSObject *obj){
//	jsval ar=OBJECT_TO_JSVAL(JS_NewArrayObject(cx, 0, NULL));
//	JS_SetProperty(cx, obj, "threads", &ar);
	JS_DefineFunction(cx, obj, "setTimeout", (void *)SetTimeout, 2, JSFUN_FAST_NATIVE);
	JS_DefineFunction(cx, obj, "clearTimeout", (void *)ClearTimeout, 1, JSFUN_FAST_NATIVE);

//	JS_SetBranchCallback(cx, branch_callback);

	return 0;
}





